#include "w25qxx.h"

/* SPI1 引脚定义 */
#define W25QXX_CS_PIN          GPIO_Pin_9        // CS引脚 - PB9
#define W25QXX_CS_GPIO_PORT    GPIOB             // CS引脚端口
#define W25QXX_CS_GPIO_CLK     RCC_APB2Periph_GPIOB  // CS引脚时钟

/* CS引脚控制宏定义 */
#define W25QXX_CS_HIGH()       GPIO_SetBits(W25QXX_CS_GPIO_PORT, W25QXX_CS_PIN)      // CS拉高
#define W25QXX_CS_LOW()        GPIO_ResetBits(W25QXX_CS_GPIO_PORT, W25QXX_CS_PIN)    // CS拉低

/**
 * @brief 初始化SPI1接口
 * @note 配置SPI1为主模式,8位数据格式
 */
static void SPI1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef SPI_InitStructure;

    // 使能SPI1和GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA | W25QXX_CS_GPIO_CLK, ENABLE);

    // 配置SPI1引脚: SCK(PA5), MISO(PA6), MOSI(PA7)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置CS引脚为推挽输出
    GPIO_InitStructure.GPIO_Pin = W25QXX_CS_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(W25QXX_CS_GPIO_PORT, &GPIO_InitStructure);
    W25QXX_CS_HIGH();  // CS默认高电平

    // SPI1配置
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 双线全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       // 主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 8位数据格式
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;         // 时钟极性:空闲时为高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        // 时钟相位:第2个边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;           // NSS信号由软件管理
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 波特率预分频值
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  // 数据传输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial = 7;            // CRC值计算的多项式
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);  // 使能SPI1
}

/**
 * @brief SPI1收发一个字节
 * @param TxData 要发送的字节
 * @return uint8_t 接收到的字节
 */
static uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
    uint8_t retry = 0;
    
    // 等待发送缓冲区空
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
    {
        if(retry++ > 200) return 0;
    }
    SPI_I2S_SendData(SPI1, TxData);
    
    retry = 0;
    // 等待接收缓冲区非空
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
    {
        if(retry++ > 200) return 0;
    }
    return SPI_I2S_ReceiveData(SPI1);
}

/**
 * @brief 初始化W25QXX
 */
void W25QXX_Init(void)
{
    SPI1_Init();  // 初始化SPI1接口
}

/**
 * @brief 读取W25QXX的ID
 * @return uint16_t 返回值如下:
 *         - 0XEF16: 表示芯片型号为W25Q64JV
 *         - 其他值: 不支持的芯片
 */
uint16_t W25QXX_ReadID(void)
{
    uint16_t Temp = 0;
    W25QXX_CS_LOW();                        // 使能器件
    SPI1_ReadWriteByte(0x90);               // 发送读取ID命令
    SPI1_ReadWriteByte(0x00);               // 发送24位地址
    SPI1_ReadWriteByte(0x00);
    SPI1_ReadWriteByte(0x00);
    Temp |= SPI1_ReadWriteByte(0xFF)<<8;    // 读取制造商ID
    Temp |= SPI1_ReadWriteByte(0xFF);       // 读取设备ID
    W25QXX_CS_HIGH();                       // 取消片选
    return Temp;
}

/**
 * @brief 等待W25QXX空闲
 */
static void W25QXX_Wait_Busy(void)
{
    uint8_t status = 0;
    do {
        W25QXX_CS_LOW();
        SPI1_ReadWriteByte(W25X_ReadStatusReg);  // 发送读取状态寄存器命令
        status = SPI1_ReadWriteByte(0xFF);       // 读取状态寄存器
        W25QXX_CS_HIGH();
    } while((status & 0x01) == 0x01);           // 等待BUSY位清空
}

/**
 * @brief 写使能
 * @note 写入数据前必须先写使能
 */
static void W25QXX_Write_Enable(void)
{
    W25QXX_CS_LOW();
    SPI1_ReadWriteByte(W25X_WriteEnable);   // 发送写使能命令
    W25QXX_CS_HIGH();
}

/**
 * @brief 从指定地址读取指定长度的数据
 * @param pBuffer 数据存储缓冲区指针
 * @param ReadAddr 读取的起始地址(24bit)
 * @param NumByteToRead 要读取的字节数
 */
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    W25QXX_CS_LOW();
    SPI1_ReadWriteByte(W25X_ReadData);          // 发送读取命令
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16));  // 发送24bit地址
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));
    SPI1_ReadWriteByte((uint8_t)ReadAddr);
    
    while(NumByteToRead--) {
        *pBuffer = SPI1_ReadWriteByte(0xFF);    // 循环读取数据
        pBuffer++;
    }
    W25QXX_CS_HIGH();
}

/**
 * @brief 在指定地址写入一页数据
 * @param pBuffer 数据存储缓冲区指针
 * @param WriteAddr 写入的起始地址(24bit)
 * @param NumByteToWrite 要写入的字节数(最大256,即一页)
 */
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    W25QXX_Write_Enable();                      // 写使能
    
    W25QXX_CS_LOW();
    SPI1_ReadWriteByte(W25X_PageProgram);       // 发送页写命令
    SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16)); // 发送24bit地址
    SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));
    SPI1_ReadWriteByte((uint8_t)WriteAddr);
    
    while(NumByteToWrite--) {
        SPI1_ReadWriteByte(*pBuffer);           // 循环写入数据
        pBuffer++;
    }
    
    W25QXX_CS_HIGH();
    W25QXX_Wait_Busy();                         // 等待写入结束
}

/**
 * @brief 擦除一个扇区
 * @param Dst_Addr 扇区地址,根据实际容量设置
 * @note 擦除一个扇区的最少时间:150ms
 */
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
    Dst_Addr *= 4096;                           // 扇区地址，4KB为一个扇区
    W25QXX_Write_Enable();                      // 写使能
    W25QXX_Wait_Busy();                         // 等待空闲
    
    W25QXX_CS_LOW();
    SPI1_ReadWriteByte(W25X_SectorErase);       // 发送扇区擦除命令
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16)); // 发送24bit地址
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
    SPI1_ReadWriteByte((uint8_t)Dst_Addr);
    W25QXX_CS_HIGH();
    
    W25QXX_Wait_Busy();                         // 等待擦除完成
}

/**
 * @brief 写入数据(在写入前会自动擦除扇区)
 * @param pBuffer 数据存储缓冲区指针
 * @param WriteAddr 写入的起始地址(24bit)
 * @param NumByteToWrite 要写入的字节数
 */
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint32_t secpos;    // 扇区地址
    uint16_t secoff;    // 扇区内偏移地址
    uint16_t secremain; // 扇区内剩余地址
    uint16_t i;
    uint8_t* W25QXX_BUF;
    
    W25QXX_BUF = pBuffer;
    secpos = WriteAddr/4096;  // 扇区地址
    secoff = WriteAddr%4096;  // 在扇区内的偏移
    secremain = 4096-secoff;  // 扇区剩余空间大小
    
    if(NumByteToWrite <= secremain) secremain = NumByteToWrite; // 不大于4096个字节
    
    while(1) {
        W25QXX_Erase_Sector(secpos);  // 擦除这个扇区
        for(i=0; i<secremain; i++) {  // 写入扇区剩余区间
            W25QXX_Write_Page(&W25QXX_BUF[i], secoff+i, 1);
        }
        if(NumByteToWrite == secremain) break; // 写入结束了
        else {  // 写入未结束
            secpos++;   // 扇区地址增1
            secoff = 0; // 偏移位置为0
            W25QXX_BUF += secremain;  // 指针偏移
            NumByteToWrite -= secremain;  // 字节数递减
            if(NumByteToWrite>4096) secremain = 4096;  // 下一个扇区还是写不完
            else secremain = NumByteToWrite;  // 下一个扇区可以写完了
        }
    }
} 